/* ****************************************************************************
 *
 * Copyright (c) Microsoft Corporation. 
 *
 * This source code is subject to terms and conditions of the Microsoft Public License. A 
 * copy of the license can be found in the License.html file at the root of this distribution. If 
 * you cannot locate the  Microsoft Public License, please send an email to 
 * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 
 * by the terms of the Microsoft Public License.
 *
 * You must not remove this notice, or any other, from this software.
 *
 *
 * ***************************************************************************/

using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;

using Microsoft.Scripting;
using Microsoft.Scripting.Runtime;

namespace IronPython.Runtime {
    internal class SymbolIdDictionaryStorage : DictionaryStorage {
        private Dictionary<SymbolId, object> _data;

        public SymbolIdDictionaryStorage() {
        }

        public SymbolIdDictionaryStorage(int count) {
            _data = new Dictionary<SymbolId, object>(count);
        }

        public override void Add(object key, object value) {
            Debug.Assert(!(key is SymbolId));

            lock (this) {
                AddNoLock(key, value);
            }
        }

        public override void AddNoLock(object key, object value) {
            EnsureData();

            string strKey = key as string;
            if (strKey != null) {
                _data[SymbolTable.StringToId(strKey)] = value;
            } else {
                GetObjectDictionary()[key] = value;
            }
        }

        public override void Add(SymbolId key, object value) {
            EnsureData();

            lock (this) _data[key] = value;
        }

        public override bool Contains(object key) {
            if (_data == null) return false;

            lock (this) {
                string strKey = key as string;
                if (strKey != null) {
                    return _data.ContainsKey(SymbolTable.StringToId(strKey));
                } else {
                    Dictionary<object, object> dict = TryGetObjectDictionary();
                    if (dict != null) {
                        return dict.ContainsKey(key);
                    }

                    return false;
                }
            }
        }

        public override bool Contains(SymbolId key) {
            if (_data == null) return false;

            lock (this) {
                return _data.ContainsKey(key);
            }
        }

        public override bool Remove(object key) {
            if (_data == null) return false;

            lock (this) {
                string strKey = key as string;
                if (strKey != null) {
                    return _data.Remove(SymbolTable.StringToId(strKey));
                } else {
                    Dictionary<object, object> dict = TryGetObjectDictionary();
                    if (dict != null) {
                        return dict.Remove(key);
                    }

                    return false;
                }
            }
        }

        public override bool TryGetValue(object key, out object value) {
            if (_data != null) {
                lock (this) {
                    string strKey = key as string;
                    if (strKey != null) {
                        return _data.TryGetValue(SymbolTable.StringToId(strKey), out value);
                    }

                    Dictionary<object, object> dict = TryGetObjectDictionary();

                    if (dict != null) {
                        return dict.TryGetValue(key, out value);
                    }
                }
            }

            value = null;
            return false;
        }

        public override bool TryGetValue(SymbolId key, out object value) {
            if (_data == null) {
                value = null;
                return false;
            }

            lock (this) {
                return _data.TryGetValue(key, out value);
            }
        }

        public override int Count {
            get {
                if (_data == null) return 0;

                lock (this) {
                    if (_data == null) return 0;

                    int count = _data.Count;
                    Dictionary<object, object> dict = TryGetObjectDictionary();
                    if (dict != null) {
                        // plus the object keys, minus the object dictionary key
                        count += dict.Count - 1;
                    }
                    return count;
                }
            }
        }

        public override void Clear() {
            _data = null;
        }

        public override List<KeyValuePair<object, object>> GetItems() {
            List<KeyValuePair<object, object>> res = new List<KeyValuePair<object, object>>();

            if (_data != null) {
                lock (this) {
                    foreach (KeyValuePair<SymbolId, object> kvp in _data) {
                        if (kvp.Key == SymbolId.Empty) continue;

                        res.Add(new KeyValuePair<object, object>(SymbolTable.IdToString(kvp.Key), kvp.Value));
                    }

                    Dictionary<object, object> dataDict = TryGetObjectDictionary();
                    if (dataDict != null) {
                        foreach (KeyValuePair<object, object> kvp in GetObjectDictionary()) {
                            res.Add(kvp);
                        }
                    }
                }
            }

            return res;
        }

        private Dictionary<object, object> TryGetObjectDictionary() {
            if (_data != null) {
                object dict;
                if (_data.TryGetValue(SymbolId.Empty, out dict)) {
                    return (Dictionary<object, object>)dict;
                }
            }

            return null;
        }

        private Dictionary<object, object> GetObjectDictionary() {
            EnsureData();

            object dict;
            if (_data.TryGetValue(SymbolId.Empty, out dict)) {
                return (Dictionary<object, object>)dict;
            }

            Dictionary<object, object> res = new Dictionary<object, object>();
            _data[SymbolId.Empty] = res;

            return res;
        }

        private void EnsureData() {
            if (_data == null) {
                Interlocked.CompareExchange<Dictionary<SymbolId, object>>(ref _data, new Dictionary<SymbolId, object>(), null);
            }
        }
    }
}
